#ifndef _M3_H
#define _M3_H

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <vector>
#include <map>
#include <string>

#include "mpi.h"

#include "parameter.h"
#include "util.h"
#include "divider.h"
#include "classifier.h"
#include "voter.h"
#include "hyper_plane.h"
#include "libsvm_parameter.h"
#include "libsvm.h"

using namespace std;

namespace M3{

  bool rank_master(int rank);
  bool rank_slave(int rank);
  bool rank_run(int rank);

  void initialize(int argc,
		  char * argv[]);
  void finalize();
  void load_train_data(const string &);
  void divide_train_data(int);
  void training_train_data();
  void classify_test_data(const string &);
  void score_test_data();

  class M3_Master{
  private:
    struct Train_Task_Info{
      int process_rank_1,process_rank_2;
      int subset_num_1,subset_num_2;
      int continue_size;

      int free_task;

      int task_num_1,task_num_2;
      int total_task;

      Train_Task_Info(int pr_1,
		      int pr_2,
		      int sn_1,
		      int sn_2,
		      int cs){
	process_rank_1=pr_1;
	process_rank_2=pr_2;
	subset_num_1=sn_1;
	subset_num_1=sn_1;
	subset_num_2=sn_2;
	continue_size=cs;

	free_task=0;
	task_num_1=subset_num_1/continue_size+((subset_num_1%continue_size)?1:0);
	task_num_2=subset_num_2/continue_size+((subset_num_2%continue_size)?1:0);
	total_task=task_num_1*task_num_2;
      }

      bool task_over(){
	return free_task>=total_task;
      }

      int left_1(){
	return (free_task/task_num_2)*continue_size;
      }

      int left_2(){
	return (free_task%task_num_2)*continue_size;
      }

      int right_1(){
	return min(left_1()+continue_size,subset_num_1)-1;
      }

      int right_2(){
	return min(left_2()+continue_size,subset_num_2)-1;
      }

      int length_1(){
	return right_1()-left_1()+1;
      }

      int length_2(){
	return right_2()-left_2()+1;
      }
    };

    struct Block_Index{
      int process;
      int index;

      bool operator < (const Block_Index & bi) const{
	return process<bi.process ||
	  index<bi.index;
      }
    };

    vector<Train_Task_Info> m_train_task_info;

    map<float,int> m_label_to_index;
    vector<float> m_index_to_label;

    map<float,vector<int> > m_label_to_process;
    map<int,double> m_process_to_label;
    map<int,int> m_process_train_data_num;
    map<int,int> m_process_train_subset_num;
    int m_free_process;
    int m_test_process_num;

    vector<Subset_Info> m_test_subset;

    float string_to_float(char * str,
			  int ll,
			  int rr);

    int string_to_int(char * str,
		      int ll,
		      int rr);

    void parse_data(char * read_buf,
		    Data_Sample * data_sample);

    void data_package(Data_Sample * sample_buf,
		      Data_Node * node_buf,
		      Data_Sample * sample_buf_send,
		      Data_Node * node_buf_send,
		      int len,
		      float sample_label,
		      bool * been_sent,
		      int & sbs_len,
		      int & nsb_len);

    void check_load_data();

    void load_train_data(string file_name, // The file's name. 
			 vector<bool> need_train_index); // The table point out which data need to be trained. 

    void check_divide_data();

    void make_train_info();

    void test_ctrl(int ctrl);

    void load_subset_config();

    bool ask_load_subset(int index);

    void classify_test_data(string file_name,
			    vector<bool> test_flag);

    void score_test_data(vector<bool> test_flag);

  public:
    M3_Master();
    void load_train_data(const string &);
    void divide_train_data();
    void training_train_data();
    void classify_test_data(const string &);
    void score_test_data();

  };

  class M3_Slave{
  private:
    int m_train_data_num;
    bool m_memory_enough;

    // debug
    static const int DEBUG_MEMORY_CAPACITY=10;

    struct Sample_Link{
      Data_Sample * sample_head;
      Sample_Link * next;
      int length;
    };
    Sample_Link * m_sample_link_head,* m_sample_link_tail;

    Data_Sample ** m_sample_arr;
    vector<Divide_Info> m_divide_situation;

    void data_unpackage(Data_Sample * sample_buf,
			Data_Node * node_buf,
			int sp_buf_len,
			int nd_buf_len);

    void check_load_data();

    void pre_divide();

    static void local_memory_scarcity(){
      throw new exception;
    }

    void check_divide_data();

    void subset_sample_package(int ll,
			       int rr,
			       Data_Sample * sample_buf,
			       int & nb_len);

    void subset_node_package(int sb_len,
			     Data_Sample * sample_buf,
			     Data_Node * node_buf);

  public:
    M3_Slave();
    ~M3_Slave();
    void load_train_data();
    void divide_train_data(int);
    void training_train_data();
    void classify_test_data();	// do nothing
  };

  class M3_Run{  
  private:
    float m_data_label_1,m_data_label_2;
    int m_data_process_1,m_data_process_2;
    int m_data_subset_num_1,m_data_subset_num_2;

    Data_Sample ** m_sample_subset_1, ** m_sample_subset_2;
    int * m_sample_subset_len_1, * m_sample_subset_len_2;
    Data_Node * m_node_buf_1,* m_node_buf_2;
    Data_Sample * m_sample_buf_1,* m_sample_buf_2;
    int m_sample_len_1,m_sample_len_2;
    int m_node_len_1,m_node_len_2;

    void subset_sample_unpackage(int subset_num,
				 int * subset_len,
				 Data_Sample * sample_buf,
				 Data_Sample ** sample_subset,
				 int & node_len);

    void subset_node_unpackage(Data_Sample * sample_buf,
			       Data_Node * node_buf,
			       int sb_len);

  public:
    void load_train_data();	// do nothing
    void divide_train_data();	// do nothing
    void training_train_data();
    void classify_test_data();
  };

  static Divider * m3_divider;
  static Classifier * m3_classifier;

  static M3_Parameter m3_parameter;
  static Parameter m3_arameter;
  static int m3_all_process_num;
  static int m3_start_slave_process_rank; // denote the slave process num start
  static int m3_subset_size;
  static int m3_continue_subset_size;

  static int M3_TAG=0;	// point out the recive tag
  static int M3_SUBSET_INFO_TAG=9999; // special tag for free response

  static MPI_Datatype MPI_Data_Node;  // need to be commit
  static MPI_Datatype MPI_Data_Sample;
  static MPI_Datatype MPI_Subset_Info;
  static int m3_my_rank;

  static M3_Master * m3_master;
  static M3_Slave * m3_slave;
  static M3_Run * m3_run;

  static ofstream debug_out;

  static double m3_start_time;
 
  const int M3_MASTER_RANK=0; // master must be rank 0

  // There are some mistake that define the follow as const, so I define them as static
  static int CTRL_MEMORY_ENOUGH=0;
  static int CTRL_MEMORY_SCARCITY=1;
  // For load
  static int CTRL_READ_DONE=0;
  static int CTRL_ALLOC_MEMORY=1;
  static int CTRL_GET_DATA=2;
  // For train
  static int CTRL_TRAIN_DONE=0;
  static int CTRL_TRAIN_CONTINUE=1;
  // For test
  static int CTRL_TEST_DONE=0;
  static int CTRL_TEST_CLEAR=1;
  static int CTRL_LOAD_SUBSET=2;
  static int CTRL_CLASSIFY_DATA=3;

  // define some length
  const int NODE_BUF_SIZE=1024*1024*100; // May be defined by para
  const int SAMPLE_BUF_SIZE=100;

  const string SUBSET_DIR="Subset/";
  const string SUBSET_CONFIG="subset.config";
  const string SCORE_DIR="Score/";
};

#endif
